<?php
header("Content-Type:text/html;charset=utf-8");
//后端显示器计时器
$second=1;
$assign=array("Jack","Lucy","Lily","Lory","Tom");
if(($socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP)) < 0) {
    echo "socket_create() 失败的原因是:".socket_strerror($sock)."\n";
}
if(($ret = socket_bind($socket,'0.0.0.0','9090')) < 0) {
    echo "socket_bind() 失败的原因是:".socket_strerror($ret)."\n";
}
if(($ret = socket_listen($socket,3)) < 0) {
    echo "socket_listen() 失败的原因是:".socket_strerror($ret)."\n";
}

$all_sockets = [$socket];    // socket 集合
do {
    $copy_sockets = $all_sockets;   // 单独拷贝一份
    
    if(count($all_sockets)>1) {
        $accountList=$all_sockets;
        array_shift($accountList);
        foreach($accountList as $key => $value){
                  $result[]=$key;
        }
        $accountNum=count($result);
        if(($second%30)==0){
            echo "current total accounts:{$accountNum}".PHP_EOL;
        }
        $data1=json_encode(array("sign"=>"list","accountNum"=>$accountNum,"accountList"=>$result));
        $result=null;
    } else {
        $data1=json_encode(array("sign"=>"none"));
        if(($second%35)==0){
            echo "current total accounts:0".PHP_EOL;
        }
    }

    //json数据，内容包括总账户数，具体账户列表
    foreach($all_sockets as $key => $value) {
        if($value === $socket) {
            continue;
        }
        
        socket_set_nonblock($value[0]);
        // 获取客户端发给服务端的内容
        @$buf = socket_read($value[0],1024);
        if(empty($buf)){
            $all_sockets[$key][1]++;
        }else{
            $all_sockets[$key][1]=1;
        }
        // 代表客户端主动关闭
        if($all_sockets[$key][1]>10) {
            unset($all_sockets[$key]);
            socket_close($value[0]);
            echo "client [{$key}] has lost connection".PHP_EOL;
            continue;
        }
        @socket_write($value[0], buildMsg($data1));
        //单对单发送消息代码
        $acceptData=getMsg($buf);
        $acceptData2=substr($acceptData,1);
        if(!empty($acceptData)) {
            $head=substr($acceptData,0,1);
            if($head === '@') {
                $acceptName=strstr($acceptData2, " ", true);
                $data=strstr($acceptData2," "); 
                $data=strstr($data,"/n/n",true);
                $data=$key.":".$data;//最新改动
                $data=json_encode(array("sign"=>"accept", "message"=>$data));
                @$sendResult=socket_write($all_sockets[$acceptName][0], buildMsg($data));
                if($sendResult==false){
                    socket_write($value[0], buildMsg(json_encode(array("sign"=>"sendResult", "message"=>"failure"))));
                }else{
                    socket_write($value[0], buildMsg(json_encode(array("sign"=>"sendResult", "message"=>"success"))));
                }
            } 
        }
        //单对单发送消息代码
           }
    
    
    // 因为客户端是长连接，如果客户端非正常断开，服务端会在 socket_accept 阻塞，现在使用 select 非阻塞模式 socket
    if(@socket_select($copy_sockets, $write, $except, 0) === false)
        exit('socket_select error!');
        
    // 接收第一次 socket 连入，连入后移除服务端 socket
    if(in_array($socket, $copy_sockets)) {
        $client= socket_accept($socket);
    
        if($client) {
            
            $buf = socket_read($client, 1024);

            // 匹配 Sec-Websocket-Key 标识
            if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/i",$buf,$match)) {
                // 需要将 Sec-WebSocket-Key 值累加字符串，并依次进行 SHA-1 加密和 base64 加密
                $key = base64_encode(sha1($match[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',true));
                // 拼凑响应内容
                $res= "HTTP/1.1 101 Switching Protocol".PHP_EOL
                      ."Upgrade: WebSocket".PHP_EOL
                      ."Connection: Upgrade".PHP_EOL
                      ."WebSocket-Location: ws://127.0.0.1:9090".PHP_EOL
                      ."Sec-WebSocket-Accept: " . $key .PHP_EOL.PHP_EOL;  // 注意这里，需要两个换行
                // 向客户端应答 Sec-WebSocket-Accept
                socket_write($client, $res, strlen($res));
                  }
                foreach($assign as $key1=>$value1) {
                    foreach($all_sockets as $key=>$value) {
                        if($key===$value1) {
                            continue 2;
                        }
                    }
                        //add him to the $all_sockets array
                        $all_sockets[$value1] = array($client,1);
                        $assignName=json_encode(array("sign"=>"name","assignName"=>$value1));
                        socket_write($client, buildMsg($assignName));
                        echo "client [{$value1}] build connection...".PHP_EOL;
                        socket_write($client, buildMsg($data1));
                        break;
                    }
             if(count($all_sockets)===6){
                socket_write($client, buildMsg('{"sign":"none2","message":"no account to assign"}'));
             }       
        }
    }
$second++;
sleep(1);
} while(true);
socket_close($socket);

// 编码服务端向客户端发送的内容
function buildMsg($msg) {
    $frame = [];
    $frame[0] = '81';
    $len = strlen($msg);
    if ($len < 126) {
        $frame[1] = $len < 16 ? '0' . dechex($len) : dechex($len);
    } else if ($len < 65025) {
        $s = dechex($len);
        $frame[1] = '7e' . str_repeat('0', 4 - strlen($s)) . $s;
    } else {
        $s = dechex($len);
        $frame[1] = '7f' . str_repeat('0', 16 - strlen($s)) . $s;
    }
    $data = '';
    $l = strlen($msg);
    for ($i = 0; $i < $l; $i++) {
        $data .= dechex(ord($msg{$i}));
    }
    $frame[2] = $data;
    $data = implode('', $frame);
    return pack("H*", $data);
}

// 解析客户端向服务端发送的内容
function getMsg($buffer) {
    $res = '';
    $len = ord($buffer[1]) & 127;
    if ($len === 126) {
        $masks = substr($buffer, 4, 4);
        $data = substr($buffer, 8);
    } else if ($len === 127) {
        $masks = substr($buffer, 10, 4);
        $data = substr($buffer, 14);
    } else {
        $masks = substr($buffer, 2, 4);
        $data = substr($buffer, 6);
    }
    for ($index = 0; $index < strlen($data); $index++) {
        $res .= $data[$index] ^ $masks[$index % 4];
    }
    return $res;
}
?>